package com.bubblegumproject.ogo.component;

import java.lang.reflect.Method;
import java.lang.reflect.Field;

/**
 * Represents a Javabean property.
 *
 * @author Azubuko Obele (buko.obele@gmail.com)
 */
public class JavaBeanProperty {

    private Method getterMethod;

    private Method setterMethod;

    private Class propertyType;

    private Class beanType;

    private String name;

    public JavaBeanProperty(Method method) {
        if (!method.getName().startsWith("get") && !method.getName().startsWith("set")) {
            throw new IllegalArgumentException(method.getName() + " is not a javabean property");
        }

        this.beanType = method.getDeclaringClass();
        // get the proper name of the property
        String capitalizedName = method.getName().substring(3);
        this.name = Character.toLowerCase(capitalizedName.charAt(0)) + capitalizedName.substring(1);

        if (method.getName().startsWith("get")) {
            this.getterMethod = method;
            propertyType = getterMethod.getReturnType();
            // look for a setter method if it exists
            for (Method m : method.getDeclaringClass().getMethods())  {
                if (m.getName().equals("set" + capitalizedName)) setterMethod = m;
            }
        } else {
            this.setterMethod = method;
            propertyType = setterMethod.getParameterTypes()[0];
            
            // look for the getter method if it exists
            for (Method m : method.getDeclaringClass().getMethods()) {
                if (m.getName().equals("get" + capitalizedName)) getterMethod = m;
            }
        }
    }

    public String getName() {
        return name;
    }

    public boolean hasGetter() {
        return getterMethod != null;
    }

    public boolean hasSetter() {
        return setterMethod != null;
    }

    public Class getPropertyType() {
        return propertyType;
    }

    public Object get(Object bean) {
        if (hasGetter()) {
            try {
                return getterMethod.invoke(bean);
            } catch (Exception e) {
                throw new RuntimeException(this + "'s getterMethod failed", e);
            }
        } else {
            throw new IllegalStateException(this + " has no getter method");
        }
    }

    public void set(Object bean, Object value) {
        if (hasSetter()) {
            try {
                setterMethod.invoke(bean, value);
            } catch (Exception e) {
                throw new RuntimeException(this + "'s setter method failed", e);
            }
        } else {
            // as a last ditch effort we can try to set the field value directly
            try {
                Field propertyField = beanType.getDeclaredField(name);
                propertyField.setAccessible(true);
                propertyField.set(bean, value);
            } catch (NoSuchFieldException nfe) {
                throw new Error(this + " can't set field directly because no field found", nfe);
            } catch (Exception e) {
                throw new Error(this + " failed to set field directly", e);
            }
        }
    }

    public String toString() {
        return "[" + getClass().getSimpleName() + " " + propertyType + ":" + name + " getter=" + getterMethod +
            " setter=" + setterMethod + "]";
    }
}
